////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.controls
{

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;
import mx.collections.CursorBookmark;
import mx.collections.IList;
import mx.collections.ItemResponder;
import mx.collections.errors.ItemPendingError;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListBase;
import mx.controls.listClasses.ListBaseSeekPending;
import mx.controls.listClasses.ListData;
import mx.controls.listClasses.ListItemRenderer;
import mx.controls.listClasses.ListRowInfo;
import mx.controls.scrollClasses.ScrollBar;
import mx.core.ClassFactory;
import mx.core.EdgeMetrics;
import mx.core.EventPriority;
import mx.core.FlexShape;
import mx.core.FlexSprite;
import mx.core.IChildList;
import mx.core.IFactory;
import mx.core.IIMESupport;
import mx.core.IInvalidating;
import mx.core.IPropertyChangeNotifier;
import mx.core.IRawChildrenContainer;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.ListEvent;
import mx.events.ListEventReason;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.managers.IFocusManager;
import mx.managers.IFocusManagerComponent;
import mx.styles.StyleManager;

use namespace mx_internal;

include "../styles/metadata/BackgroundDisabledColor.as"

[IconFile("List.png")]

[DataBindingInfo("acceptedTypes", "{ dataProvider: { label: &quot;String&quot; } }")]

[DefaultProperty("dataProvider")]

[DefaultBindingProperty(source="selectedItem", destination="dataProvider")]

[DefaultTriggerEvent("change")]

[AccessibilityClass(implementation="mx.accessibility.ListAccImpl")]

/**
 *  Dispatched when the user releases the mouse button while over an item,
 *  tabs to the List or within the List, or in any other way
 *  attempts to edit an item.
 *
 *  @eventType mx.events.ListEvent.ITEM_EDIT_BEGINNING
 */
[Event(name="itemEditBeginning", type="mx.events.ListEvent")]

/**
 *  Dispatched when the <code>editedItemPosition</code> property is set
 *  and the item can be edited.
 *
 *  @eventType mx.events.ListEvent.ITEM_EDIT_BEGIN
 */
[Event(name="itemEditBegin", type="mx.events.ListEvent")]

/**
 *  Dispatched when an item editing session is ending for any reason.
 *
 *  @eventType mx.events.ListEvent.ITEM_EDIT_END
 */
[Event(name="itemEditEnd", type="mx.events.ListEvent")]

/**
 *  Dispatched when an item renderer gets focus, which can occur if the user
 *  clicks on an item in the List control or navigates to the item using a 
 *  keyboard.
 *  Only dispatched if the list item is editable.
 *
 *  @eventType mx.events.ListEvent.ITEM_FOCUS_IN
 */
[Event(name="itemFocusIn", type="mx.events.ListEvent")]

/**
 *  Dispatched when an item renderer loses the focus, which can occur if the 
 *  user clicks another item in the List control or outside the list, 
 *  or uses the keyboard to navigate to another item in the List control
 *  or outside the List control.
 *  Only dispatched if the list item is editable.
 *
 *  @eventType mx.events.ListEvent.ITEM_FOCUS_OUT
 */
[Event(name="itemFocusOut", type="mx.events.ListEvent")]

/**
 *  The List control displays a vertical list of items.
 *  Its functionality is very similar to that of the SELECT
 *  form element in HTML.
 *  If there are more items than can be displayed at once, it
 *  can display a vertical scroll bar so the user can access
 *  all items in the list.
 *  An optional horizontal scroll bar lets the user view items
 *  when the full width of the list items is unlikely to fit.
 *  The user can select one or more items from the list, depending
 *  on the value of the <code>allowMultipleSelection</code> property.
 *
 *  @mxml
 *
 *  <p>The <code>&lt;mx:List&gt;</code> tag inherits all the tag attributes
 *  of its superclass, and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;mx:List
 *    <b>Properties</b>
 *    editable="false|true"
 *    editedItemPosition="<i>No default</i>"
 *    editorDataField="text"
 *    editorHeightOffset="0"
 *    editorUsesEnterKey="false|true"
 *    editorWidthOffset="0"
 *    editorXOffset="0"
 *    editorYOffset="0"
 *    imeMode="null"    
 *    itemEditor="TextInput"
 *    itemEditorInstance="<i>Current item editor</i>"
 *    rendererIsEditor="false|true"
 *    
 *    <b>Styles</b>
 *    backgroundDisabledColor="0xDDDDDD"
 *    
 *    <b>Events</b>
 *    itemEditBegin="<i>No default</i>"
 *    itemEditEnd="<i>No default</i>"
 *    itemEditBeginning="<i>No default</i>"
 *    itemFocusIn="<i>No default</i>"
 *    itemFocusOut="<i>No default</i>"
 *  /&gt;
 *  </pre>
 *
 *  @includeExample examples/SimpleList.mxml
 *
 *  @see mx.events.ListEvent
 */
public class List extends ListBase implements IIMESupport
{
	include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class mixins
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *  Placeholder for mixin by ListAccImpl.
	 */
	mx_internal static var createAccessibilityImplementation:Function;

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function List()
	{
		super();

		listType = "vertical";

		bColumnScrolling = false;

		itemRenderer = new ClassFactory(ListItemRenderer);

		_horizontalScrollPolicy = ScrollPolicy.OFF;
		_verticalScrollPolicy = ScrollPolicy.AUTO;

		defaultColumnCount = 1;
		defaultRowCount = 7;

		// Register default handlers for item editing and sorting events.
		addEventListener(ListEvent.ITEM_EDIT_BEGINNING,
						 itemEditorItemEditBeginningHandler,
						 false, EventPriority.DEFAULT_HANDLER);
		addEventListener(ListEvent.ITEM_EDIT_BEGIN,
						 itemEditorItemEditBeginHandler,
						 false, EventPriority.DEFAULT_HANDLER);
		addEventListener(ListEvent.ITEM_EDIT_END,
						 itemEditorItemEditEndHandler,
						 false, EventPriority.DEFAULT_HANDLER);
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

    /**
     *  A reference to the currently active instance of the item editor, 
	 *  if it exists.
     *
     *  <p>To access the item editor instance and the new item value when an 
	 *  item is being edited, you use the <code>itemEditorInstance</code> 
	 *  property. The <code>itemEditorInstance</code> property
     *  is not valid until after the event listener for
     *  the <code>itemEditBegin</code> event executes. Therefore, you typically
     *  only access the <code>itemEditorInstance</code> property from within 
	 *  the event listener for the <code>itemEditEnd</code> event.</p>
     *
     *  <p>The <code>itemEditor</code> property defines the
     *  class of the item editor
     *  and, therefore, the data type of the item editor instance.</p>
     *
     *  <p>You do not set this property in MXML.</p>
     */
    public var itemEditorInstance:IListItemRenderer;

    /**
     *  A reference to the item renderer
     *  in the DataGrid control whose item is currently being edited.
     *
     *  <p>From within an event listener for the <code>itemEditBegin</code>
     *  and <code>itemEditEnd</code> events,
     *  you can access the current value of the item being edited
     *  using the <code>editedItemRenderer.data</code> property.</p>
     */
    public function get editedItemRenderer():IListItemRenderer
	{
		if (!itemEditorInstance) return null;

		return listItems[actualRowIndex][actualColIndex];
	}

	/**
	 *  @private
	 *  true if we want to block editing on mouseUp
	 */
	private var dontEdit:Boolean = false;

	/**
	 *  @private
	 *  true if we want to block editing on mouseUp
	 */
	private var losingFocus:Boolean = false;

	/**
	 *  @private
	 *  true if we're in the endEdit call.  Used to handle
	 *  some timing issues with collection updates
	 */
	private var inEndEdit:Boolean = false;

	// last known position of item editor
	private var actualRowIndex:int;
	private	var actualColIndex:int = 0;

	//--------------------------------------------------------------------------
	//
	//  Overridden properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  baselinePosition
	//----------------------------------

    /**
     *  The baseline position for the control.
	 *  If there are any items in the list, the baseline position is
	 *  computed based on the baseline position of item renderer.
	 *  The baseline position is the y-coordinate of the first line of text 
	 *  of the component. 
     */
    override public function get baselinePosition():Number
    {
    	if (listItems.length && listItems[0].length)
	        return borderMetrics.top + cachedPaddingTop + listItems[0][0].baselinePosition;

    	return NaN;
    }

    //----------------------------------
    //  maxHorizontalScrollPosition
    //----------------------------------

	/**
	 *  @private
	 *  The maximum value of <code>horizontalScrollPosition</code> in pixels.
	 *  The default value is NaN.
	 *  If this value is NaN, the first time the List is layed out
	 *  it sets <code>horizontalScrollPosition</code> to twice the width.
	 *  You can calculate the exact value of
	 *  <code>maxHorizontalScrollPosition</code> by calling
	 *  the <code>measureWidthOfItems()</code> method on the widest string,
	 *  and then subtracting the width of the List and the width of its borders.
	 *
	 *  <p>For example if the fifth item is the widest,
	 *  you set <code>maxHorizontalScrollPosition</code> like this:<br></br>
	 *  <pre>list.maxHorizontalScrollPosition = list.measureWidthOfItems(5, 1) - (list.width -
	 *  list.viewMetrics.left - list.viewMetrics.right)</pre></p>
	 */
	override public function set maxHorizontalScrollPosition(value:Number):void
	{
		super.maxHorizontalScrollPosition = value;
		scrollAreaChanged = true;

		invalidateDisplayList();
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  editable
	//----------------------------------

    [Inspectable(category="General")]

    /**
	 *  A flag that indicates whether or not the user can edit
	 *  items in the data provider.
     *  If <code>true</code>, the item renderers in the control are editable.
     *  The user can click on an item renderer to open an editor.
     *
	 *  @default false
     */
    public var editable:Boolean = false;

    //----------------------------------
    //  itemEditor
    //----------------------------------

	[Inspectable(category="Data")]

    /**
     *  The class factory for the item editor to use for the control, if the 
	 *  <code>editable</code> property is set to <code>true</code>. 
	 *
     *  @default new ClassFactory(mx.controls.TextInput)
     */
    public var itemEditor:IFactory = new ClassFactory(TextInput);

    //----------------------------------
    //  editorDataField
    //----------------------------------

	[Inspectable(category="Data")]

	/**
	 *  The name of the property of the item editor that contains the new
	 *  data for the list item.
	 *  For example, the default <code>itemEditor</code> is
	 *  TextInput, so the default value of the <code>editorDataField</code> property is
	 *  <code>"text"</code>, which specifies the <code>text</code> property of the
	 *  the TextInput control.
	 */
	public var editorDataField:String = "text";

    //----------------------------------
    //  editorHeightOffset
    //----------------------------------

	[Inspectable(defaultValue="0")]
	
	/**
     *  The height of the item editor, in pixels, relative to the size of the 
	 *  item renderer. This property can be used to make the editor overlap
	 *  the item renderer by a few pixels to compensate for a border around the
	 *  editor.  
	 *  <p>Changing these values while the editor is displayed
	 *  will have no effect on the current editor, but will affect the next
	 *  item renderer that opens an editor.</p>
     *
     *  @default 0
     */
    public var editorHeightOffset:Number = 0;

    //----------------------------------
    //  editorWidthOffset
    //----------------------------------

	[Inspectable(defaultValue="0")]
	
	/**
     *  The width of the item editor, in pixels, relative to the size of the 
	 *  item renderer. This property can be used to make the editor overlap
	 *  the item renderer by a few pixels to compensate for a border around the
	 *  editor.
	 *  <p>Changing these values while the editor is displayed
	 *  will have no effect on the current editor, but will affect the next
	 *  item renderer that opens an editor.</p>
     *
     *  @default 0
     */
    public var editorWidthOffset:Number = 0;

    //----------------------------------
    //  editorXOffset
    //----------------------------------

	[Inspectable(defaultValue="0")]
	
	/**
     *  The x location of the upper-left corner of the item editor,
     *  in pixels, relative to the upper-left corner of the item.
     *  This property can be used to make the editor overlap
	 *  the item renderer by a few pixels to compensate for a border around the
	 *  editor.
	 *  <p>Changing these values while the editor is displayed
	 *  will have no effect on the current editor, but will affect the next
	 *  item renderer that opens an editor.</p>
	 * 
     *  @default 0
     */
    public var editorXOffset:Number = 0;

    //----------------------------------
    //  editorYOffset
    //----------------------------------

	[Inspectable(defaultValue="0")]
	
	/**
     *  The y location of the upper-left corner of the item editor,
     *  in pixels, relative to the upper-left corner of the item.
     *  This property can be used to make the editor overlap
	 *  the item renderer by a few pixels to compensate for a border around the
	 *  editor.
	 *  <p>Changing these values while the editor is displayed
	 *  will have no effect on the current editor, but will affect the next
	 *  item renderer that opens an editor.</p>
     *
     *  @default 0
     */
    public var editorYOffset:Number = 0;

    //----------------------------------
    //  editorUsesEnterKey
    //----------------------------------

	[Inspectable(defaultValue="false")]

	/**
     *  A flag that indicates whether the item editor uses Enter key.
	 *  If this property is set to <code>true</code>, the item editor uses the Enter key and the
	 *  List will not look for the Enter key and move the editor in
	 *  response.
	 *  <p>Changing this value while the editor is displayed
	 *  will have no effect on the current editor, but will affect the next
	 *  item renderer that opens an editor.</p>
	 *
     *  @default false
     */
    public var editorUsesEnterKey:Boolean = false;

	//----------------------------------
	//  enabled
	//----------------------------------

    [Inspectable(category="General", enumeration="true,false", defaultValue="true")]

    /**
     *  @private
     */
    override public function set enabled(value:Boolean):void
    {
        super.enabled = value;

		if (itemEditorInstance)
			endEdit(ListEventReason.OTHER);

        invalidateDisplayList();
    }

	//----------------------------------
	//  editedItemPosition
	//----------------------------------

    /**
     *  @private
     */
    private var bEditedItemPositionChanged:Boolean = false;

    /**
     *  @private
	 *  undefined means we've processed it
	 *  null means don't put up an editor
	 *  {} is the coordinates for the editor
     */
    private var _proposedEditedItemPosition:*;

    /**
     *  @private
     */
    private var _editedItemPosition:Object;

    [Bindable("itemFocusIn")]

    /**
     *  The column and row index of the item renderer for the
	 *  data provider item being edited, if any.
     *
     *  <p>This Object has two fields, <code>columnIndex</code> and 
	 *  <code>rowIndex</code>,
     *  the zero-based column and item indexes of the item.
     *  For a List control, the <code>columnIndex</code> property is always 0;
     *  for example: <code>{columnIndex:0, rowIndex:3}</code>.</p>
     *
     *  <p>Setting this property scrolls the item into view and
	 *  dispatches the <code>itemEditBegin</code> event to
     *  open an item editor on the specified item,
     *  </p>
     */
    public function get editedItemPosition():Object
    {
		if (_editedItemPosition)
			return {rowIndex: _editedItemPosition.rowIndex,
				columnIndex: 0};
		else
			return _editedItemPosition;
    }

    /**
     *  @private
     */
    public function set editedItemPosition(value:Object):void
    {
		var newValue:Object = {rowIndex: value.rowIndex,
			columnIndex: 0};

        setEditedItemPosition(newValue);
    }

    //----------------------------------
    //  rendererIsEditor
    //----------------------------------

	[Inspectable(category="Data", defaultValue="false")]
    
	/**
     *  Specifies whether the item renderer is also an item 
	 *  editor. If this property is <code>true</code>, Flex
     *  ignores the <code>itemEditor</code> property.
     *
     *  @default false
     */
    public var rendererIsEditor:Boolean = false;

    //----------------------------------
    //  imeMode
    //----------------------------------

    /**
     *  @private
	 *  Storage for the imeMode property.
     */
    private var _imeMode:String;

    /**
     *  Specifies the IME (input method editor) mode.
     *  The IME enables users to enter text in Chinese, Japanese, and Korean.
     *  Flex sets the specified IME mode when the control gets focus,
     *  and sets it back to the previous value when the control loses focus.
     *
     * <p>The flash.system.IMEConversionMode class defines constants for the
	 *  valid values for this property.
     *  You can also specify <code>null</code> to specify no IME.</p>
     *  
     *  @see flash.system.IME
     *  @see flash.system.IMEConversionMode
     *  
     *  @default null
     */
    public function get imeMode():String
    {
        return _imeMode;
    }

    /**
     *  @private
     */
    public function set imeMode(value:String):void
    {
        _imeMode = value;
    }

	//--------------------------------------------------------------------------
	//
	//  Overridden methods
	//
	//--------------------------------------------------------------------------

    [Inspectable(category="Data", defaultValue="undefined")]

    /**
     *  @private
     */
    override public function set dataProvider(value:Object):void
    {
		if (itemEditorInstance)
			endEdit(ListEventReason.OTHER);
		
		super.dataProvider = value;
	}

	/**
	 *  @private
	 *  Called by the initialize() method of UIComponent
	 *  to hook in the accessibility code.
	 */
	override protected function initializeAccessibility():void
	{
		if (createAccessibilityImplementation != null)
			createAccessibilityImplementation(this);
	}

    /**
     *  @private
     */
	override protected function commitProperties():void
	{
		super.commitProperties();

		if (itemsNeedMeasurement)
		{
			itemsNeedMeasurement = false;
			if (isNaN(explicitRowHeight))
			{
				if (iterator)
				{
					var paddingTop:Number = getStyle("paddingTop");
					var paddingBottom:Number = getStyle("paddingBottom");

					// trace("calculate height " + index + " " + count);
					var item:IListItemRenderer = getMeasuringRenderer();

					var ww:Number = 200;
					if (listContent.width)
						ww = listContent.width;
					item.explicitWidth = ww;

					setupRendererFromData(item, iterator.current);

					var rh:int = item.getExplicitOrMeasuredHeight() + paddingTop + paddingBottom;

					// unless specified otherwise, rowheight defaults to 20
					setRowHeight(Math.max(rh, 20));
				}
				else
					setRowHeight(20);
			}
			if (isNaN(explicitColumnWidth))
				setColumnWidth(measureWidthOfItems(0, (explicitRowCount < 1) ? defaultRowCount : explicitRowCount));
		}
	}

    /**
     *  @private
	 *  The measuredWidth is widest of the items in the first set of rows it will display.
	 *  If the rowCount property has been set it will measure that many rows, otherwise
	 *  it will measure 7 rows and use the widest.
	 *  The measuredHeight is based on the height of one line of text or 20 pixels, whichever
	 *  is greater.  Thus the measuredHeight will depend on fonts related styles like fontSize.
	 *  Then that height is multiplied by 7 or rowCount if it has been specified.
	 *  
     */
	override protected function measure():void
	{
		super.measure();

        var o:EdgeMetrics = viewMetrics;
		measuredMinWidth = DEFAULT_MEASURED_MIN_WIDTH; // room for scrollbar and room for some of the renderer

		// if we've been layed out once (initialized)
		// then if no explicit rowCount or rowHeight, just use the current
		// height.  Otherwise, if the data changes and has variable rowheight
		// the height can shift and cause re-layout.
		if (initialized && variableRowHeight && explicitRowCount < 1 && isNaN(explicitRowHeight))
			measuredHeight = height;
	}

	/**
	 *  @private
	 */
	override protected function configureScrollBars():void
	{
		var rowCount:int = listItems.length;
		if (rowCount == 0) return;

		// if there is more than one row and it is a partial row we dont count it
		if (rowCount > 1 && rowInfo[rowCount - 1].y + rowInfo[rowCount-1].height > listContent.height)
			rowCount--;

		// offset, when added to rowCount, is the index of the dataProvider
		// item for that row.  IOW, row 10 in listItems is showing dataProvider
		// item 10 + verticalScrollPosition - lockedRowCount - 1;
		var offset:int = verticalScrollPosition - lockedRowCount - 1;
		// don't count filler rows at the bottom either.
		var fillerRows:int = 0;
		// don't count filler rows at the bottom either.
		while (rowCount && listItems[rowCount - 1].length == 0)
		{
			if (collection && rowCount + offset >= collection.length)
			{
				rowCount--;
				++fillerRows;
			}
			else
				break;
		}

		// we have to scroll up.  We can't have filler rows unless the scrollPosition is 0
		if (verticalScrollPosition > 0 && fillerRows > 0)
		{
			if (adjustVerticalScrollPositionDownward(rowCount))
				return;
		}

		var colCount:int = listItems[0].length;
		var oldHorizontalScrollBar:Object = horizontalScrollBar;
		var oldVerticalScrollBar:Object = verticalScrollBar;
		var roundedWidth:int = Math.round(unscaledWidth);
		var length:int = collection ? collection.length - lockedRowCount: 0;
		var numRows:int = rowCount - lockedRowCount;
		setScrollBarProperties((isNaN(_maxHorizontalScrollPosition)) ?
							Math.round(listContent.width) :
							Math.round(_maxHorizontalScrollPosition + roundedWidth),
							roundedWidth, length, numRows);
		maxVerticalScrollPosition = Math.max(length - numRows, 0);

	}

	/**
	 *  @private
	 *  Makes verticalScrollPosition smaller until it is 0 or there
	 *  are no empty rows.  This is needed if we're scrolled to the
	 *  bottom and something is deleted or the rows resize so more
	 *  rows can be shown.
	 */
    private function adjustVerticalScrollPositionDownward(rowCount:int):Boolean
	{
		var bookmark:CursorBookmark = iterator.bookmark;

		// add up how much space we're currently taking with valid items
		var h:Number = 0;
		
		var ch:Number = 0;
		var n:int;
		var j:int;

		var paddingTop:Number = getStyle("paddingTop");
		var paddingBottom:Number = getStyle("paddingBottom");

		h = rowInfo[rowCount - 1].y + rowInfo[rowCount - 1].height;
		h = listContent.height - h;

		// back up one
		var numRows:int = 0;
		try
		{
			var more:Boolean = iterator.movePrevious();
		}
		catch (e:ItemPendingError)
		{
			more = false;
		}
		if (!more)
		{
			// reset to 0;
			super.verticalScrollPosition = 0;
			try
			{
				iterator.seek(CursorBookmark.FIRST, 0);
			}
			catch (e:ItemPendingError)
			{
				lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0);
				e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
												lastSeekPending));
				iteratorValid = false;
				invalidateList();
				return true;
			}
			updateList();
			return true;
		}

		var	item:IListItemRenderer = getMeasuringRenderer();
		item.explicitWidth = listContent.width;

		// now work backwards to see how many more rows we need to create
		while (h > 0 && more)
		{
			var data:Object;
			
			if (more)
			{
				data = iterator.current;
				setupRendererFromData(item, data);
				ch = variableRowHeight ? item.getExplicitOrMeasuredHeight() + paddingBottom + paddingTop : rowHeight;
			}
			
			h -= ch;
		
			try 
			{
				more = iterator.movePrevious();
				numRows++;
			}
			catch (e:ItemPendingError)
			{
				// if we run out of data, assume all remaining rows are the size of the previous row
				more = false;
			}
		}

		// if we overrun, go back one.
		if (h < 0)
			numRows--;

		iterator.seek(bookmark, 0);
		
		verticalScrollPosition = Math.max(0, verticalScrollPosition - numRows);

		// make sure we get through configureScrollBars w/o coming in here.
		if (numRows > 0 && !variableRowHeight)
			configureScrollBars();

		return numRows > 0;
	}

	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);

		setRowCount(listItems.length);

		if (bEditedItemPositionChanged)
		{
			bEditedItemPositionChanged = false;
			commitEditedItemPosition(_proposedEditedItemPosition);
			_proposedEditedItemPosition = undefined;
		}

		drawRowBackgrounds();
	}

	/**
	 *  @private
	 */
	override protected function drawRowBackgrounds():void
	{
        var rowBGs:Sprite = Sprite(listContent.getChildByName("rowBGs"));
		if (!rowBGs)
		{
			rowBGs = new FlexSprite();
			rowBGs.mouseEnabled = false;
			rowBGs.name = "rowBGs";
			listContent.addChildAt(rowBGs, 0)
		}

		var colors:Array;

		colors = getStyle("alternatingItemColors");

		if (!colors)
			return;

		StyleManager.getColorNames(colors);

		var curRow:int = 0;
		var i:int = 0;
		var n:int = listItems.length;

		while (curRow < n)
		{
            drawRowBackground(rowBGs, i++, rowInfo[curRow].y, rowInfo[curRow].height, colors[curRow % colors.length], curRow);
			curRow++;
		}

		while (rowBGs.numChildren > n)
		{
			rowBGs.removeChildAt(rowBGs.numChildren - 1);
		}
	}

    /**
	 *  Draws a row background 
	 *  at the position and height specified. This creates a Shape as a
	 *  child of the input Sprite and fills it with the appropriate color.
	 *  This method also uses the <code>backgroundAlpha</code> style property 
	 *  setting to determine the transparency of the background color.
	 * 
	 *  @param s A Sprite that will contain a display object
	 *  that contains the graphics for that row.
	 *
	 *  @param rowIndex The row's index in the set of displayed rows. The
	 *  header does not count; the top most visible row has a row index of 0.
	 *  This is used to keep track of the objects used for drawing
	 *  backgrounds so that a particular row can reuse the same display object
	 *  even though the index of the item that the row is rendering has changed.
	 *
	 *  @param y The suggested y position for the background.
	 * 
	 *  @param height The suggested height for the indicator.
	 * 
	 *  @param color The suggested color for the indicator.
	 * 
	 *  @param dataIndex The index of the item for that row in the
	 *  data provider. For example, this can be used to color the 10th item differently.
     */
    protected function drawRowBackground(s:Sprite, rowIndex:int,
											y:Number, height:Number, color:uint, dataIndex:int):void
	{
		// trace("drawRowBackground " + rowIndex + " " + color);

		var bg:Shape;
		if (rowIndex < s.numChildren)
		{
			bg = Shape(s.getChildAt(rowIndex));
		}
		else
		{
			bg = new FlexShape();
			bg.name = "rowBackground";
			s.addChild(bg);
		}

		// Height is usually as tall is the items in the row,
		// but not if it would extend below the bottom of listContent.
		var height:Number = Math.min(
			rowInfo[rowIndex].height,
			listContent.height - rowInfo[rowIndex].y);

		bg.y = rowInfo[rowIndex].y;

		var g:Graphics = bg.graphics;
		g.clear();
		g.beginFill(color, getStyle("backgroundAlpha"));
		g.drawRect(0, 0, listContent.width, height);
		g.endFill();
	}

    /**
     *  Creates a new ListData instance and populates the fields based on
	 *  the input data provider item. The Tree control overrides this method to add
	 *  additional Tree-related fields.
     *  
     *  @param data The data provider item used to populate the ListData.
     *  @param uid The UID for the item.
     *  @param rowNum The index of the item in the data provider.
     *  
     *  @return A newly constructed ListData object.
     */
	protected function makeListData(data:Object, uid:String,
								 rowNum:int):BaseListData
	{
		return new ListData(itemToLabel(data), itemToIcon(data), labelField, uid, this, rowNum);
	}

	/**
	 *  @private
	 */
	override protected function makeRowsAndColumns(left:Number, top:Number,
												right:Number, bottom:Number,
												firstCol:int, firstRow:int,
												byCount:Boolean = false, rowsNeeded:uint = 0):Point
	{
		// trace(this, "makeRowsAndColumns " + left + " " + top + " " + right + " " + bottom + " " + firstCol + " " + firstRow);
		listContent.allowItemSizeChangeNotification = false;

		var paddingLeft:Number = getStyle("paddingLeft");
		var paddingRight:Number = getStyle("paddingRight");

		var xx:Number = left + paddingLeft - horizontalScrollPosition;
		var ww:Number = right - paddingLeft - paddingRight;
		var yy:Number;
		var hh:Number;

		var bSelected:Boolean = false;
		var bHighlight:Boolean = false;
		var bCaret:Boolean = false;

		var i:int;
		var j:int;

		var colNum:int = lockedColumnCount;
		var rowNum:int = lockedRowCount;
		var rowsMade:int = 0;

		var item:IListItemRenderer;
		var oldItem:IListItemRenderer;
		var rowData:BaseListData;
		var data:Object;
		var uid:String;
		var more:Boolean = true;
		var valid:Boolean = true;
		var rh:Number;

			yy = top;
			rowNum = firstRow;
			more = (iterator != null && !iterator.afterLast && iteratorValid);

			while ((!byCount && yy < bottom) || (byCount && rowsNeeded > 0))
			{
				if (byCount)
					rowsNeeded--;

				valid = more;
				data = more ? iterator.current : null;

				uid = null;

				if (!listItems[rowNum])
					listItems[rowNum] = [];

				if (valid)
				{
					item = listItems[rowNum][colNum];
					uid = itemToUID(data);
					if (!item || itemToUID(item.data) != uid)
					{
						if (freeItemRenderers.length)
						{
							item = freeItemRenderers.pop();
							// trace("reused item " + item);
						}
						else
						{
							item = itemRenderer.newInstance();
							item.styleName = listContent;
							// trace("created item " + item);
							listContent.addChild(DisplayObject(item));
						}

						oldItem = listItems[rowNum][colNum];
						if (oldItem)
							addToFreeItemRenderers(oldItem);
						listItems[rowNum][colNum] = item;
					}
					rowData = makeListData(data, uid, rowNum);
					rowMap[item.name] = rowData;

					if (item is IDropInListItemRenderer)
					{
						if (data != null)
							IDropInListItemRenderer(item).listData = rowData;
						else
							IDropInListItemRenderer(item).listData = null;
					}

					item.data = data;
					item.enabled = enabled;
					item.visible = true;
					if (uid)
						visibleData[uid] = item;

					item.explicitWidth = ww;

					if ((item is IInvalidating)
						&& (wordWrapChanged || variableRowHeight))
						IInvalidating(item).invalidateSize();

					layoutManager.validateClient(item, true);

					hh = variableRowHeight ?
						 item.getExplicitOrMeasuredHeight() +
						 cachedPaddingTop + cachedPaddingBottom :
						 rowHeight;
					rh = item.getExplicitOrMeasuredHeight();
					item.setActualSize(ww, variableRowHeight ? rh : rowHeight - cachedPaddingTop - cachedPaddingBottom);
					item.move(xx, yy + cachedPaddingTop);
				}
				else
				{
					// trace("not valid");
					// if we've run out of data, we dont make renderers
					// and we inherit the previous row's height or rowHeight
					// if it is the first row.
					hh = rowNum > 0 ? rowInfo[rowNum - 1].height : rowHeight;

					oldItem = listItems[rowNum][colNum];
					if (oldItem)
					{
						addToFreeItemRenderers(oldItem);
						listItems[rowNum].splice(colNum, 1);
					}
				}
				bSelected = selectedData[uid] != null;
				bHighlight = highlightUID == uid;
				bCaret = caretUID == uid;
				rowInfo[rowNum] = new ListRowInfo(yy, hh, uid, data);
				if (valid)
					drawItem(item, bSelected, bHighlight, bCaret);
				yy += hh;
				rowNum++;
				rowsMade++;
				if (iterator && more)
				{
					try
					{
						more = iterator.moveNext();
					}
					catch (e:ItemPendingError)
					{
						lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0)
						e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
														lastSeekPending));
						more = false;
						iteratorValid = false;
					}
				}
			}
			// byCount means we're making rows and wont get all the way to the bottom
			// so we skip this cleanup pass
			if (!byCount)
			{
				// delete extra rows
				while (rowNum < listItems.length)
				{
					var rr:Array = listItems.pop();
					rowInfo.pop();
					while (rr.length)
					{
						item = rr.pop();
						addToFreeItemRenderers(item);
					}
				}
			}

        if (itemEditorInstance)
		{
            listContent.setChildIndex(DisplayObject(itemEditorInstance),
									  listContent.numChildren - 1);
	        item = listItems[actualRowIndex][actualColIndex];
	        var rowInfo:ListRowInfo = rowInfo[actualRowIndex];
			if (item && !rendererIsEditor)
			{
				var dx:Number = editorXOffset;
				var dy:Number = editorYOffset;
				var dw:Number = editorWidthOffset;
				var dh:Number = editorHeightOffset;
 				layoutEditor(item.x + dx, rowInfo.y + dy,
							Math.min(item.width + dw, listContent.width - listContent.x - itemEditorInstance.x),
							Math.min(rowInfo.height + dh, listContent.height - listContent.y - itemEditorInstance.y));

			}
		}

		listContent.allowItemSizeChangeNotification = variableRowHeight;

		return new Point(colNum, rowsMade);
	}

	/**
	 *  Positions the item editor instance at the suggested position
	 *  with the suggested dimensions. The Tree control overrides this
	 *  method and adjusts the position to compensate for indentation
	 *  of the renderer.
	 *
	 *  @param x The suggested x position for the indicator.
	 *  @param y The suggested y position for the indicator.
	 *  @param w The suggested width for the indicator.
	 *  @param h The suggested height for the indicator.
	 */
 	protected function layoutEditor(x:int, y:int, w:int, h:int):void
 	{
 		itemEditorInstance.move(x, y);
 		itemEditorInstance.setActualSize(w, h);
 	}

	/**
	 *  @private
	 */
	override protected function scrollHandler(event:Event):void
	{
		// TextField.scroll bubbles so you might see it here
		if (event is ScrollEvent)
		{
			if (itemEditorInstance)
				endEdit(ListEventReason.OTHER);

			if (!liveScrolling &&
				ScrollEvent(event).detail == ScrollEventDetail.THUMB_TRACK)
			{
				return;
			}

			var scrollBar:ScrollBar = ScrollBar(event.target);
			var pos:Number = scrollBar.scrollPosition;
			var delta:int;
			var o:EdgeMetrics;

		    removeClipMask();

			if (scrollBar == verticalScrollBar)
			{
				delta = pos - verticalScrollPosition;
				
				super.scrollHandler(event);
				
				if (Math.abs(delta) >= listItems.length - lockedRowCount)
				{
					try
					{
						iterator.seek(CursorBookmark.CURRENT, delta);
					}
					catch (e:ItemPendingError)
					{
						lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, delta)
						e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
														lastSeekPending));
						// trace("IPE in UpdateDisplayList");
						iteratorValid = false;
						// don't do anything, we'll repaint when the data arrives
					}
					var bookmark:CursorBookmark = iterator.bookmark;
					 //if we scrolled more than the number of scrollable rows
					clearIndicators();
					visibleData = {};
					makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
					iterator.seek(bookmark, 0);
				}
				else if (delta != 0)
					scrollVertically(pos, Math.abs(delta), Boolean(delta > 0));
				// if variable rowheight, we have to recalibrate the scrollbars thumb size
				// on each scroll, otherwise you can't scroll down to a bunch of fat rows
				// at the bottom of a list.
				if (variableRowHeight)
					configureScrollBars();

				drawRowBackgrounds();
			}
			else
			{
				delta = pos - _horizontalScrollPosition;
				
				super.scrollHandler(event);
				
				scrollHorizontally(pos, Math.abs(delta), Boolean(delta > 0));
			}

	        // if needed, add a clip mask to the items in the last row of the list
		    addClipMask(false);
		}
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *  List scrolls horizontally by pixels.
	 */
    override protected function scrollHorizontally(pos:int, deltaPos:int, scrollUp:Boolean):void
	{
		var n:int = listItems.length;
		var paddingLeft:Number = getStyle("paddingLeft");
		for (var i:int = 0; i < n; i++)
		{
			if (listItems[i].length)
				listItems[i][0].x = -pos + paddingLeft;
		}
	}

    /**
	 *  @private
     */
    protected function getMeasuringRenderer():IListItemRenderer
    {
		var item:IListItemRenderer =
			IListItemRenderer(listContent.getChildByName("hiddenItem"));
		if (!item)
		{
			item = itemRenderer.newInstance();
			item.name = "hiddenItem";
			item.visible = false;
			item.styleName = listContent;
			listContent.addChild(DisplayObject(item));
		}
		
		return item;
    }
    
    /**
	 *  @private
     */
    protected function setupRendererFromData(item:IListItemRenderer, data:Object):void
    {
		if (item is IDropInListItemRenderer)
		{
			if (data != null)
				IDropInListItemRenderer(item).listData = makeListData(data, itemToUID(data), 0);
			else
				IDropInListItemRenderer(item).listData = null;
		}

		item.data = data;

		if (item is IInvalidating)
			IInvalidating(item).invalidateSize();

		layoutManager.validateClient(item, true);
    }    

	/**
	 *  @private
	 */
	override public function measureWidthOfItems(index:int = -1, count:int = 0):Number
	{
		if (count == 0)
			count = (collection) ? collection.length : 0;

		// if empty collection, don't measure anything
		if (collection && collection.length == 0)
			count = 0;

		var item:IListItemRenderer = getMeasuringRenderer();
		item.explicitWidth = NaN;	// gets set in measureHeightOfItems

		var w:Number = 0;

		var bookmark:CursorBookmark = (iterator) ? iterator.bookmark : null;
		if (index != -1 && iterator)
			iterator.seek(CursorBookmark.FIRST, index);

		var rw:Number;
		var more:Boolean = iterator != null;
		for (var i:int = 0; i < count; i++)
		{
			var data:Object;
			if (more)
			{
				data = iterator.current;
				setupRendererFromData(item, data);

				rw = item.measuredWidth;
				w = Math.max(w, rw);
			}

			if (more)
			{
				try
				{
					more = iterator.moveNext();
				}
				catch (e:ItemPendingError)
				{
					// if we run out of data, assume all remaining rows are the size of the previous row
					more = false;
				}
			}
		}

		if (iterator)
			iterator.seek(bookmark, 0);

		if (w == 0)
		{
			if (explicitWidth)
				return explicitWidth;
			else
				return DEFAULT_MEASURED_WIDTH;
		}

		var paddingLeft:Number = getStyle("paddingLeft");
		var paddingRight:Number = getStyle("paddingRight");
		w += paddingLeft + paddingRight

		return w;
	}

	/**
	 *  @private
	 */
	override public function measureHeightOfItems(index:int = -1, count:int = 0):Number
	{
		if (count == 0)
			count = (collection) ? collection.length : 0;

		var paddingTop:Number = getStyle("paddingTop");
		var paddingBottom:Number = getStyle("paddingBottom");

		// trace("calculate height " + index + " " + count);
        var item:IListItemRenderer = getMeasuringRenderer();

		var ww:Number = 200;
		if (listContent.width)
			ww = listContent.width;
		item.explicitWidth = ww;

		var h:Number = 0;

		var bookmark:CursorBookmark = (iterator) ? iterator.bookmark : null;
		if (index != -1 && iterator)
			iterator.seek(CursorBookmark.FIRST, index);

		var rh:Number = rowHeight;
		var more:Boolean = iterator != null;
		for (var i:int = 0; i < count; i++)
		{
			var data:Object;
			if (more)
			{
				rh = rowHeight;
				data = iterator.current;
				setupRendererFromData(item, data);

				if (variableRowHeight)
					rh = item.getExplicitOrMeasuredHeight() + paddingTop + paddingBottom;
			}
			h += rh;

			if (more)
			{
				try
				{
					more = iterator.moveNext();
				}
				catch (e:ItemPendingError)
				{
					// if we run out of data, assume all remaining rows are the size of the previous row
					more = false;
				}
			}
		}

		if (iterator)
			iterator.seek(bookmark, 0);

		return h;
	}

    /**
     *  @private
     */
    override protected function mouseEventToItemRenderer(event:MouseEvent):IListItemRenderer
    {
        var r:IListItemRenderer = super.mouseEventToItemRenderer(event);
        return r == itemEditorInstance ? null : r;
    }

	[Inspectable(category="Data")]

	/**
	 *  @private
	 */
	override public function set itemRenderer(value:IFactory):void
	{
		super.itemRenderer = value;

		if (listContent)
		{
			var item:IListItemRenderer =
				IListItemRenderer(listContent.getChildByName("hiddenItem"));
			if (item)
				listContent.removeChild(DisplayObject(item));
		}
	}

    /**
     *  @private
     */
    private function setEditedItemPosition(coord:Object):void
    {
		bEditedItemPositionChanged = true;
		_proposedEditedItemPosition = coord;
		invalidateDisplayList();
	}

    /**
     *  @private
     *  focus an item in the grid - harder than it looks
     */
    private function commitEditedItemPosition(coord:Object):void
    {
        if (!enabled || !editable)
			return;

		// just give focus back to the itemEditorInstance
		if (itemEditorInstance && coord &&
			itemEditorInstance is IFocusManagerComponent &&
			_editedItemPosition.rowIndex == coord.rowIndex)
		{
			IFocusManagerComponent(itemEditorInstance).setFocus();
			return;
		}

        // dispose of any existing editor, saving away its data first
        if (itemEditorInstance)
		{
			var reason:String;
			if (!coord)
				reason = ListEventReason.OTHER;
			else
				reason = ListEventReason.NEW_ROW;
			// trace("calling endEdit from commitEditedItemPosition", _editedItemPosition.rowIndex);
            if (!endEdit(reason) && reason != ListEventReason.OTHER)
				return;
		}

        // store the value
        _editedItemPosition = coord;

        // allow setting of undefined to dispose item editor
        if (!coord)
            return;

		if (dontEdit)
		{
			return;
		}

		var rowIndex:int = coord.rowIndex;
		var colIndex:int = coord.columnIndex;

		// trace("setEditedItemPosition ", coord.rowIndex, selectedIndex);

		if (selectedIndex != coord.rowIndex)
			commitSelectedIndex(coord.rowIndex);

		var actualLockedRows:int = lockedRowCount;
		var lastRowIndex:int = verticalScrollPosition + listItems.length - 1;
		var partialRow:int = (rowInfo[listItems.length - 1].y + rowInfo[listItems.length - 1].height > listContent.height) ? 1 : 0;

		// actual row/column is the offset into listItems
		if (rowIndex > actualLockedRows)
		{
			// not a locked editable row make sure it is on screen
			if (rowIndex < verticalScrollPosition + actualLockedRows)
				verticalScrollPosition = rowIndex - actualLockedRows;
			else
			{
				// variable row heights means that we can't know how far to scroll sometimes so we loop
				// until we get it right
				while (rowIndex > lastRowIndex ||
					// we're the last row, and we're partially visible, but we're not
					// the top scrollable row already
					(rowIndex == lastRowIndex && rowIndex > verticalScrollPosition + actualLockedRows &&
						partialRow))
				{
					if (verticalScrollPosition == maxVerticalScrollPosition)
						break;
					verticalScrollPosition = Math.min(verticalScrollPosition + (rowIndex > lastRowIndex ? rowIndex - lastRowIndex : partialRow), maxVerticalScrollPosition);
					lastRowIndex = verticalScrollPosition + listItems.length - 1;
					partialRow = (rowInfo[listItems.length - 1].y + rowInfo[listItems.length - 1].height > listContent.height) ? 1 : 0;
				}
			}

			actualRowIndex = rowIndex - verticalScrollPosition;

		}
		else
		{
			if (rowIndex == actualLockedRows)
				verticalScrollPosition = 0;

			actualRowIndex = rowIndex;
		}

		var bm:EdgeMetrics = borderMetrics;

		actualColIndex = colIndex;

        // get the actual references for the column, row, and item
        var item:IListItemRenderer = listItems[actualRowIndex][actualColIndex];

        var event:ListEvent =
            new ListEvent(ListEvent.ITEM_EDIT_BEGIN, false, true);
			// ITEM_EDIT events are cancelable
        event.rowIndex = _editedItemPosition.rowIndex;
		event.itemRenderer = item;
		dispatchEvent(event);

		// user may be trying to change the edited item
		if (bEditedItemPositionChanged)
		{
			bEditedItemPositionChanged = false;
			commitEditedItemPosition(_proposedEditedItemPosition);
			_proposedEditedItemPosition = undefined;
		}

		if (!itemEditorInstance)
		{
			// assume that editing was cancelled
			commitEditedItemPosition(null);
		}
	}

    /**
     *  Creates the item editor for the item renderer at the
	 *  <code>editedItemPosition</code> using the editor
     *  specified by the <code>itemEditor</code> property.
     *
	 *  <p>This method sets the editor instance as the 
	 *  <code>itemEditorInstance</code> property.</p>
	 *
	 *  <p>You can call this method only from within the event listener
	 *  for the <code>itemEditBegin</code> event. To create an editor 
	 *  at other times, set the <code>editedItemPosition</code> property 
	 *  to generate the <code>itemEditBegin</code> event.</p>
	 *
	 *  @param colIndex The column index. Flex sets the value of this property to 0 for a List control.
	 *
	 *  @param rowIndex The index in the data provider of the item to be 
	 *  edited.
     */
    public function createItemEditor(colIndex:int, rowIndex:int):void
	{
		colIndex = 0;

		if (rowIndex > lockedRowCount)
			rowIndex -= verticalScrollPosition;

        var item:IListItemRenderer = listItems[rowIndex][colIndex];
        var rowData:ListRowInfo = rowInfo[rowIndex];
        // rendererIsEditor is part of the IListItemRenderer interface. It allows the item itself to do the editing
        if (!rendererIsEditor)
        {
            var dx:Number = 0;
            var dy:Number = -2;
            var dw:Number = 0;
            var dh:Number = 4;
            // if this isn't implemented, use an input control as editor
            if (!itemEditorInstance)
            {
                dx = editorXOffset;
                dy = editorYOffset;
                dw = editorWidthOffset;
                dh = editorHeightOffset;
                itemEditorInstance = itemEditor.newInstance();
                itemEditorInstance.styleName = this;
                listContent.addChild(DisplayObject(itemEditorInstance));
            }
            listContent.setChildIndex(DisplayObject(itemEditorInstance), listContent.numChildren - 1);
            // give it the right size, look and placement
            itemEditorInstance.visible = true;
 			layoutEditor(item.x + dx, rowData.y + dy,
 							 Math.min(item.width + dw, listContent.width - listContent.x - itemEditorInstance.x),
							 Math.min(rowData.height + dh, listContent.height - listContent.y - itemEditorInstance.y));
			DisplayObject(itemEditorInstance).addEventListener("focusOut", itemEditorFocusOutHandler);

        }
        else
        {
            // if the item is an itemEditorInstance, we'll use it
            itemEditorInstance = item;
        }

        // listen for keyStrokes on the itemEditorInstance (which lets the grid supervise for ESC/ENTER)
        DisplayObject(itemEditorInstance).addEventListener(KeyboardEvent.KEY_DOWN, editorKeyDownHandler);
		// we disappear on any mouse down outside the editor
		// use weak reference
        stage.addEventListener(MouseEvent.MOUSE_DOWN, editorMouseDownHandler, true, 0, true);

    }

    /**
     *  @private
     *  Determines the next item to navigate to by using the Tab key.
     *  If the item to be focused falls out of range (the end or beginning
	 *  of the grid), moves the focus outside the grid.
     */
    private function findNextItemRenderer(shiftKey:Boolean):Boolean
    {
		if (!_editedItemPosition)
			return false;

		// some other thing like a collection change has changed the
		// position, so bail and wait for commit to reset the editor.
		if (_proposedEditedItemPosition !== undefined)
			return false;

	    var rowIndex:int = _editedItemPosition.rowIndex;
		var columnIndex:int = _editedItemPosition.columnIndex;
        // modify direction with SHIFT (up or down)
        var newIndex:int = _editedItemPosition.rowIndex +
						   (shiftKey ? -1 : 1);
        // only move if we're within range
        if (newIndex < collection.length && newIndex >= 0)
            rowIndex = newIndex;
		else
		{
            // if we've fallen off the rows, we need to leave the grid. get rid of the editor
            setEditedItemPosition(null);
			// set focus back to the grid so default handler will move it to the next component
			losingFocus = true;
			setFocus();
            return false;
		}

		// send event to create the new one
		var listEvent:ListEvent =
			new ListEvent(ListEvent.ITEM_EDIT_BEGINNING, false, true);
			// ITEM_EDIT events are cancelable
		listEvent.rowIndex = rowIndex;
		listEvent.columnIndex = columnIndex;
		dispatchEvent(listEvent);

        return true;
    }

    /**
     *  Closes an item editor that is currently open on an item. 
	 *  You typically call this method only from within the event listener 
	 *  for the <code>itemEditEnd</code> event, after
     *  you call the <code>preventDefault()</code> method to prevent
     *  the default event listener from executing.
     */
    public function destroyItemEditor():void
    {
        // trace("destroyItemEditor");
        if (itemEditorInstance)
        {
            DisplayObject(itemEditorInstance).removeEventListener(KeyboardEvent.KEY_DOWN, editorKeyDownHandler);
            stage.removeEventListener(MouseEvent.MOUSE_DOWN, editorMouseDownHandler, true);

            var event:ListEvent =
                new ListEvent(ListEvent.ITEM_FOCUS_OUT);
            event.rowIndex = _editedItemPosition.rowIndex;
			event.itemRenderer = editedItemRenderer;
            dispatchEvent(event);

            if (!rendererIsEditor)
            {
				if (itemEditorInstance && itemEditorInstance is UIComponent)
					UIComponent(itemEditorInstance).drawFocus(false);

                // must call removeChild() so FocusManager.lastFocus becomes null
                listContent.removeChild(DisplayObject(itemEditorInstance));
            }
            itemEditorInstance = null;
        }
    }

    /**
	 *  Stops the editing of an item in the data provider.
	 *  When the user finished editing an item, the control calls this method.  
	 *  It dispatches the <code>itemEditEnd</code> event to start the process
	 *  of copying the edited data from
	 *  the <code>itemEditorInstance</code> to the data provider and hiding the 
	 *  <code>itemEditorInstance</code>.
	 *  
	 *  @param reason A constant defining the reason for the event 
	 *  (such as "CANCELLED", "NEW_ROW", or "OTHER"). 
	 *  The value must be a member of the ListEventReason class.
	 *  
	 *  @return Returns <code>true</code> if <code>preventDefault()</code> is not called.
	 *  Otherwise, <code>false</code>.
	 *  
	 *  @see mx.events.ListEventReason
     */
    protected function endEdit(reason:String):Boolean
    {
		// this happens if the renderer is removed asynchronously ususally with FDS
		if (!editedItemRenderer)
			return true;

		inEndEdit = true;

        var listEvent:ListEvent =
            new ListEvent(ListEvent.ITEM_EDIT_END, false, true);
			// ITEM_EDIT events are cancelable
        listEvent.rowIndex = editedItemPosition.rowIndex;
		listEvent.itemRenderer = editedItemRenderer;
		listEvent.reason = reason;
        dispatchEvent(listEvent);
		// set a flag to not open another edit session if the item editor is still up
		// this means somebody wants the old edit session to stay.
		dontEdit = itemEditorInstance != null;
		// trace("dontEdit", dontEdit);
		
		if (!dontEdit && reason == ListEventReason.CANCELLED)
		{
			losingFocus = true;
			setFocus();
		}
		
		inEndEdit = false;

		return !(listEvent.isDefaultPrevented())
	}

    /**
     *  @private
     */
    override protected function mouseDownHandler(event:MouseEvent):void
    {
		// trace(">>mouseDownHandler");
        var r:IListItemRenderer;
		var s:Sprite;

		r = mouseEventToItemRenderer(event);

        var isItemEditor:Boolean = itemRendererContains(itemEditorInstance, DisplayObject(event.target));

		// If it isn't an item, or an external item editor do default behavior
        if (!isItemEditor)
		{
			if (r && r.data)
			{
				var pos:Point = itemRendererToIndices(r);

				var bEndedEdit:Boolean = true;

				if (itemEditorInstance)
				{
					bEndedEdit = endEdit(ListEventReason.NEW_ROW);
				}

				// if we didn't end edit session, don't do default behavior (call super)
				if (!bEndedEdit)
					return;
			}
			else
			{
				// trace("end edit?");
				if (itemEditorInstance)
					endEdit(ListEventReason.OTHER);
			}

            super.mouseDownHandler(event);
		}
		// trace("<<mouseDownHandler");
    }

    /**
     *  @private
     */
    override protected function mouseUpHandler(event:MouseEvent):void
    {
		var listEvent:ListEvent;
        var r:IListItemRenderer;
		var s:Sprite;
		var n:int;
		var i:int;

		r = mouseEventToItemRenderer(event);

        super.mouseUpHandler(event);

        if (r && r.data && r != itemEditorInstance)
        {
            var pos:Point = itemRendererToIndices(r);

            if (editable && !dontEdit)
            {
                listEvent = new ListEvent(ListEvent.ITEM_EDIT_BEGINNING, false, true);
				// ITEM_EDIT events are cancelable
                listEvent.rowIndex = pos.y;
				listEvent.columnIndex = 0;
				listEvent.itemRenderer = r;
                dispatchEvent(listEvent);
                //dispatchReplayableInteraction(listEvent);
            }
        }
    }

    /**
     *  @private
     *  when the grid gets focus, focus an item
     */
    override protected function focusInHandler(event:FocusEvent):void
    {
        // trace(">>DGFocusIn ", selectedIndex);

		if (event.target != this)
		{
			// trace("subcomponent got focus ignoring");
	        // trace("<<DGFocusIn ");
			return;
		}

		if (losingFocus)
		{
			losingFocus = false;
			// trace("losing focus via tab");
	        // trace("<<DGFocusIn ");
			return;
		}

        super.focusInHandler(event);

		if (editable && !isPressed) // don't do this if we're mouse focused
        {
            var foundOne:Boolean = editedItemPosition != null;

			// start somewhere
            if (!_editedItemPosition)
            {
                _editedItemPosition = { rowIndex: 0, columnIndex: 0 };
				foundOne = (listItems.length && listItems[0].length > 0);
            }

            if (foundOne)
			{
				// trace("setting focus", _editedItemPosition.columnIndex, _editedItemPosition.rowIndex);
				setEditedItemPosition(_editedItemPosition);
			}

            // if (foundOne)
			//	callLater(setEditedItemPosition, [ _editedItemPosition ]);
        }

        if (editable)
		{
            addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);
            addEventListener(MouseEvent.MOUSE_DOWN, mouseFocusChangeHandler);
		}
        // trace("<<DGFocusIn ");
    }

    /**
     *  @private
     *  when the grid loses focus, close the editor
     */
    override protected function focusOutHandler(event:FocusEvent):void
    {
		// trace(">>DGFocusOut " + itemEditorInstance + " " + event.relatedObject, event.target);
		if (event.target == this)
		{
			super.focusOutHandler(event);
		}

		// we're done if item editor is losing focus back to grid.  Usually happens
		// when someone clicks out of the editor onto a new item.
		if (event.relatedObject == this && itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
			return;

		// just leave if the cell renderer is losing focus to nothing while its editor exists. 
		// this happens when we make the cell renderer invisible as we put up the editor
		// if the renderer can have focus.
		if (event.relatedObject == null && itemRendererContains(editedItemRenderer, DisplayObject(event.target)))
			return;

		// just leave if item editor is losing focus to nothing.  Usually happens
		// when someone clicks out of the textfield
		if (event.relatedObject == null && itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
			return;

		// however, if we're losing focus to anything other than the editor or the grid
		// hide the editor;
		if (itemEditorInstance && (!event.relatedObject || !itemRendererContains(itemEditorInstance, event.relatedObject)))
		{
			// trace("call edit item from focus out");
			endEdit(ListEventReason.OTHER);
			removeEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);
			removeEventListener(MouseEvent.MOUSE_DOWN, mouseFocusChangeHandler);
		}
		// trace("<<DGFocusOut " + itemEditorInstance + " " + event.relatedObject);
    }

	/**
	 *  @private
	 */
	private function deactivateHandler(event:Event):void
	{
		// trace("List deactivating");
		// if stage losing activation, set focus to DG so when we get it back
		// we popup an editor again
		if (itemEditorInstance)
		{
			endEdit(ListEventReason.OTHER);
			losingFocus = true;
			setFocus();
		}

	}

	/**
	 *  @private
	 */
	override protected function keyDownHandler(event:KeyboardEvent):void
	{
		if (itemEditorInstance)
			return;

		super.keyDownHandler(event);
	}

    /**
     *  @private
     */
    private function editorMouseDownHandler(event:MouseEvent):void
    {
        if (!itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
            endEdit(ListEventReason.OTHER);
    }

    /**
     *  @private
     */
    private function editorKeyDownHandler(event:KeyboardEvent):void
    {
        // ESC just kills the editor, no new data
        if (event.keyCode == Keyboard.ESCAPE)
        {
            endEdit(ListEventReason.CANCELLED);
        }
        else if (event.ctrlKey && event.charCode == 46)
        {   // Check for Ctrl-.
            endEdit(ListEventReason.CANCELLED);
        }
        else if (event.charCode == Keyboard.ENTER && event.keyCode != 229)
        {
			// multiline editors can take the enter key.
			if (editorUsesEnterKey)
				return;

            // Enter edits the item, moves down a row
            // The 229 keyCode is for IME compatability. When entering an IME expression,
            // the enter key is down, but the keyCode is 229 instead of the enter key code.
            // Thanks to Yukari for this little trick...
            if (endEdit(ListEventReason.NEW_ROW))
				if (!dontEdit)
					findNextEnterItemRenderer(event);
        }
    }

    /**
     *  @private
     *  find the next item down from the currently edited item, and focus it.
     */
    private function findNextEnterItemRenderer(event:KeyboardEvent):void
    {
		// some other thing like a collection change has changed the
		// position, so bail and wait for commit to reset the editor.
		if (_proposedEditedItemPosition !== undefined)
			return;

		var rowIndex:int = _editedItemPosition.rowIndex;
		var columnIndex:int = _editedItemPosition.columnIndex;
        // modify direction with SHIFT (up or down)
        var newIndex:int = _editedItemPosition.rowIndex +
						   (event.shiftKey ? -1 : 1);
        // only move if we're within range
        if (newIndex < collection.length && newIndex >= 0)
            rowIndex = newIndex;

		// send event to create the new one
		var listEvent:ListEvent =
			new ListEvent(ListEvent.ITEM_EDIT_BEGINNING, false, true);
			// ITEM_EDIT events are cancelable
		listEvent.rowIndex = rowIndex;
		listEvent.columnIndex = 0;
		dispatchEvent(listEvent);
    }

    /**
     *  @private
     *  This gets called when the focus is changed by using the mouse.
     */
    private function mouseFocusChangeHandler(event:MouseEvent):void
    {
        // trace("mouseFocus handled by " + this);

        if (itemEditorInstance &&
            !event.isDefaultPrevented() &&
            itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
        {
            event.preventDefault();
        }
    }

    /**
     *  @private
     *  This gets called when the focus is changed by pressing the Tab key.
     */
    private function keyFocusChangeHandler(event:FocusEvent):void
    {
        // trace("tabHandled by " + this);

        if (event.keyCode == Keyboard.TAB &&
            ! event.isDefaultPrevented() &&
            findNextItemRenderer(event.shiftKey))
        {
            event.preventDefault();
        }
    }

    /**
     *  @private
     *  Hides the itemEditorInstance if it loses focus.
     */
	private function itemEditorFocusOutHandler(event:FocusEvent):void
	{
		// trace("itemEditorFocusOut " + event.relatedObject);
		if (event.relatedObject && contains(event.relatedObject))
			return;

		// ignore textfields losing focus on mousedowns
		if (!event.relatedObject)
			return;

		// trace("endEdit from itemEditorFocusOut");
		if (itemEditorInstance)
			endEdit(ListEventReason.OTHER);
	}

	/**
	 *  @private
	 */
	private function itemEditorItemEditBeginningHandler(event:ListEvent):void
	{
		// trace("itemEditorItemEditBeginningHandler");
		if (!event.isDefaultPrevented())
			setEditedItemPosition({columnIndex: event.columnIndex, rowIndex: event.rowIndex});
		else if (!itemEditorInstance)
		{
			_editedItemPosition = null;
			// return focus to the grid w/o selecting an item
			editable = false;
			setFocus();
			editable = true;
		}
	}

    /**
     *  @private
     *  create the editor for the item.
     */
    private function itemEditorItemEditBeginHandler(event:ListEvent):void
	{
		// trace("listening for deactivate");
		// weak reference to stage
		stage.addEventListener(Event.DEACTIVATE, deactivateHandler, false, 0, true);

		if (!event.isDefaultPrevented())
		{
			createItemEditor(event.columnIndex, event.rowIndex);

			// trace("beginEditHandler", event.rowIndex, editedItemRenderer.data);

			if (editedItemRenderer is IDropInListItemRenderer && itemEditorInstance is IDropInListItemRenderer)
				IDropInListItemRenderer(itemEditorInstance).listData = IDropInListItemRenderer(editedItemRenderer).listData;
			// if rendererIsEditor, don't apply the data as the data may have already changed in some way.
			// This can happen if clicking on a checkbox rendererIsEditor as the checkbox will try to change
			// its value as we try to stuff in an old value here.
			if (!rendererIsEditor)
				itemEditorInstance.data = editedItemRenderer.data;

			if (itemEditorInstance is IInvalidating)
				IInvalidating(itemEditorInstance).validateNow();

			if (itemEditorInstance is IIMESupport)
				IIMESupport(itemEditorInstance).imeMode = imeMode;

			var fm:IFocusManager = focusManager;
			// trace("setting focus to item editor");
			if (itemEditorInstance is IFocusManagerComponent)
				fm.setFocus(IFocusManagerComponent(itemEditorInstance));
			fm.defaultButtonEnabled = false;

			var event:ListEvent =
				new ListEvent(ListEvent.ITEM_FOCUS_IN);
			event.rowIndex = _editedItemPosition.rowIndex;
			event.itemRenderer = itemEditorInstance;
			dispatchEvent(event);
		}
	}

	/**
	 *  @private
	 *  save off the data and get rid of the editor
	 */
	private function itemEditorItemEditEndHandler(event:ListEvent):void
	{
		if (!event.isDefaultPrevented())
		{
			var bChanged:Boolean = false;
			var bFieldChanged:Boolean = false;

			var newData:Object = itemEditorInstance[editorDataField];
			var data:Object = event.itemRenderer.data;

			// trace("itemEditEndHandler", event.rowIndex, data);

			if (data is String)
			{
				if (!(newData is String))
					newData = newData.toString();
			}
			else if (data is uint)
			{
				if (!(newData is uint))
					newData = uint(newData);
			}
			else if (data is int)
			{
				if (!(newData is int))
					newData = int(newData);
			}
			else if (data is Number)
			{
				if (!(newData is int))
					newData = Number(newData);
			}
			else	// assume some sort of object
			{
				bFieldChanged = true;
				try {
					data[labelField] = newData;
					if (!(data is IPropertyChangeNotifier))
					{
						collection.itemUpdated(data, labelField);
					}
				}
				catch (e:Error)
				{
					trace("attempt to write to", labelField, "failed.  You may need a custom ITEM_EDIT_END handler");
				}
			}
			if (!bFieldChanged)
			{
				if (data != newData)
				{
					bChanged = true;
					data = newData;
				}
				if (bChanged)
				{
					if (collection is IList)
						IList(collection).setItemAt(data, event.rowIndex);
					else
						trace("attempt to update collection failed.  You may need a custom ITEM_EDIT_END handler");
				}
			}
			if (event.itemRenderer is IDropInListItemRenderer)
			{
				var listData:BaseListData = BaseListData(IDropInListItemRenderer(event.itemRenderer).listData);
				listData.label = itemToLabel(data);
				IDropInListItemRenderer(event.itemRenderer).listData = listData;
			}
			event.itemRenderer.data = data;
		}
		else
		{
			if (event.reason != ListEventReason.OTHER)
			{
				if (itemEditorInstance && _editedItemPosition)
				{
					// edit session is continued so restore focus and selection
					if (selectedIndex != _editedItemPosition.rowIndex)
						selectedIndex = _editedItemPosition.rowIndex;
					var fm:IFocusManager = focusManager;
					// trace("setting focus to item editor", selectedIndex);
					if (itemEditorInstance is IFocusManagerComponent)
						fm.setFocus(IFocusManagerComponent(itemEditorInstance));
				}
			}
		}

		if (event.reason == ListEventReason.OTHER || !event.isDefaultPrevented())
		{
	        destroyItemEditor();
		}
	}

	/**
	 *  @private
	 */
	override protected function drawHighlightIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
	{
		super.drawHighlightIndicator(indicator, 0, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
	}

	/**
	 *  @private
	 */
	override protected function drawCaretIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
	{
		super.drawCaretIndicator(indicator, 0, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
	}

	/**
	 *  @private
	 */
	override protected function drawSelectionIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
	{
		super.drawSelectionIndicator(indicator, 0, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
	}

    /**
     *  @private
     */
    override protected function mouseWheelHandler(event:MouseEvent):void
    {
		if (itemEditorInstance)
			endEdit(ListEventReason.OTHER);

		super.mouseWheelHandler(event);
	}

    /**
     *  @private
     *  Catches any events from the model. Optimized for editing one item.
     *  @param eventObj
     */
    override protected function collectionChangeHandler(event:Event):void
    {
        if(event is CollectionEvent)
        {
        	var ceEvent:CollectionEvent = CollectionEvent(event)
			// if we get a remove while editing it may invalidate the edit position
			if (ceEvent.kind == CollectionEventKind.REMOVE)
			{
				if (editedItemPosition)
				{
					//trace("editedItemPosition", editedItemPosition.rowIndex);
					if (collection.length == 0)
						setEditedItemPosition(null); // nothing left to edit
					else if (ceEvent.location <= editedItemPosition.rowIndex)
					{
						if (inEndEdit)
							_editedItemPosition = { columnIndex : editedItemPosition.columnIndex, 
												rowIndex : Math.max(0, editedItemPosition.rowIndex - ceEvent.items.length)};
						else
							setEditedItemPosition({ columnIndex : editedItemPosition.columnIndex, 
												rowIndex : Math.max(0, editedItemPosition.rowIndex - ceEvent.items.length)});
					}
				}
			}
        }

        super.collectionChangeHandler(event);

    }
}

}
